//
//  WTextView.swift
//
//
//  Created by Wang Fan on 2022/3/29.
//

import UIKit
import CoreText
import CoreFoundation
import CoreGraphics

fileprivate class GlyphArcInfo{
    var width:CGFloat = 0
    var angle:CGFloat = 0
}


public class WArcTextView: UIView {
    
    public init(){
        super.init(frame: .zero)
        self.backgroundColor = .clear
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    public enum TextAlignment {
        case left
        case center
        case right
    }
    
    public var attributeString:NSAttributedString? = nil {
        didSet{
            self.setNeedsDisplay()
        }
    }
    public var radius: CGFloat = 10 {
        didSet{
            self.setNeedsDisplay()
        }
    }
    
    public var startAngle:CGFloat = .pi{
        didSet{
            self.setNeedsDisplay()
        }
    }
    
    public var endAngle: CGFloat = 0 {
        didSet{
            self.setNeedsDisplay()
        }
    }
    
    public var textAlignment: TextAlignment = .center {
        didSet{
            self.setNeedsDisplay()
        }
    }
    
    public var showReferenceLine: Bool = false {
        didSet{
            self.setNeedsDisplay()
        }
    }
    
    public var centerOffset: CGPoint = .zero {
        didSet{
            self.setNeedsDisplay()
        }
    }
    
    public override func draw(_ rect: CGRect) {
        super.draw(rect)
        
        guard  let ctx = UIGraphicsGetCurrentContext(), let attributeString = self.attributeString else {
            return
        }
        
        let line = CTLineCreateWithAttributedString( attributeString as CFAttributedString )
        
        
        let glyphArcInfo = Self.prepareGlyphArcInfo(line: line, radius: self.radius, align: self.textAlignment, startAngle: self.startAngle, endAngle: self.endAngle)
        
        ctx.textMatrix = .identity
        ctx.saveGState()
        
        
        ctx.translateBy(x: rect.midX + self.centerOffset.x, y: rect.midY + self.centerOffset.y )
        
        
        if self.showReferenceLine {
            ctx.beginPath()
            ctx.addArc(center: .zero, radius: self.radius, startAngle: -self.startAngle, endAngle: -self.endAngle, clockwise: false)
            ctx.setStrokeColor(UIColor.red.cgColor)
            ctx.strokePath()
        }
        
        
        // 开始绘制文本， 先将文本坐标改成平躺式，
        // Rotate the context 90 degrees counterclockwise.
        ctx.rotate(by: .pi / 2)
        
        /*
         Now for the actual drawing. The angle offset for each glyph relative to the previous glyph has already been calculated; with that information in hand, draw those glyphs overstruck and centered over one another, making sure to rotate the context after each glyph so the glyphs are spread along a semicircular path.
         文本坐标永远是(0,radius), 通过旋转坐标系来实现弧形
         */
        var textPosition:CGPoint = .init(x: 0, y: self.radius)
        ctx.textPosition = textPosition
        
        let runArray = CTLineGetGlyphRuns(line)
        let runCount = CFArrayGetCount(runArray)
        
        var glyphOffset: CFIndex = 0
        for runIndex in 0 ..< runCount {
            let run = unsafeBitCast(CFArrayGetValueAtIndex(runArray, runIndex), to: CTRun.self)
            let runGlyphCount = CTRunGetGlyphCount(run)
//            var    drawSubstitutedGlyphsManually = false;
//            let key = Unmanaged.passRetained(kCTFontAttributeName).autorelease().toOpaque()
//            let runFont = CFDictionaryGetValue(CTRunGetAttributes(run), key)
            
            /*
             Determine if we need to draw substituted glyphs manually. Do so if the runFont is not the same as the overall font.
             */
            
            for runGlyphIndex in 0 ..< runGlyphCount {
                //            for (; runGlyphIndex < runGlyphCount; runGlyphIndex++) {
                let glyphRange = CFRangeMake(runGlyphIndex, 1)
                //                CGContextRotateCTM(context, -(glyphArcInfo[runGlyphIndex + glyphOffset].angle));
                ctx.rotate(by: (glyphArcInfo[runGlyphIndex + glyphOffset].angle))
                // Center this glyph by moving left by half its width.
                let glyphWidth = glyphArcInfo[runGlyphIndex + glyphOffset].width
                let halfGlyphWidth = glyphWidth / 2.0
                let positionForThisGlyph = CGPoint(x:textPosition.x - halfGlyphWidth, y:textPosition.y);
                
                // Glyphs are positioned relative to the text position for the line, so offset text position leftwards by this glyph's width in preparation for the next glyph.
                textPosition.x -= glyphWidth;
                
                var textMatrix = CTRunGetTextMatrix(run).scaledBy(x: -1, y: 1)
                textMatrix.tx = -positionForThisGlyph.x;
                textMatrix.ty = positionForThisGlyph.y;
                
                ctx.textMatrix = textMatrix
                
                CTRunDraw(run, ctx, glyphRange);
                
            }
            
            glyphOffset += runGlyphCount;
        }
        
        ctx.restoreGState()
        
    }
    
    
    fileprivate static func prepareGlyphArcInfo(line:CTLine, radius:CGFloat, align: WArcTextView.TextAlignment, startAngle:CGFloat, endAngle:CGFloat) -> [GlyphArcInfo] {
        
        var results:[GlyphArcInfo] = []
        
        let runsArray =  CTLineGetGlyphRuns(line)
        
        for index in 0 ..< CFArrayGetCount(runsArray) {
            //        let run = CFArrayGetValueAtIndex(runsArray, index) as! CTRun
            
            let run = unsafeBitCast(CFArrayGetValueAtIndex(runsArray, index), to: CTRun.self)
            
            let runGlyphCount = CTRunGetGlyphCount(run)
            
            for runGlyphIndex in 0 ..< runGlyphCount {
                
                let arcInfo = GlyphArcInfo()
                arcInfo.width = CTRunGetTypographicBounds(run, CFRangeMake(runGlyphIndex, 1), nil, nil, nil)
                
                results.append(arcInfo)
            }
            
        }
        
        let lineLength: Double = CTLineGetTypographicBounds(line, nil, nil, nil)
        
        let lineArc = lineLength / ( radius * .pi ) * .pi
        let areaArc = startAngle - endAngle
        var prevHalfWidth: CGFloat = results[0].width / 2
        
        let offsetArc: CGFloat = {
           
            switch align {
            case .left:
                return .pi - startAngle
            case .center:
                return .pi - startAngle + ( areaArc - lineArc ) / 2  - prevHalfWidth / ( radius * areaArc ) * .pi * 1.5
            case .right:
                return .pi - startAngle + areaArc - lineArc - prevHalfWidth / ( radius * areaArc ) * .pi / 2
            }
        }()
        

        
        results[0].angle = ( prevHalfWidth / lineLength ) * .pi + offsetArc
        
        for lineGlyphIndex in 1 ..< results.count {
            let halfWidth = results[lineGlyphIndex].width / 2
            let prevCenterToCenter = prevHalfWidth + halfWidth
            
            results[lineGlyphIndex].angle = ( prevCenterToCenter / lineLength ) * lineArc
            
            prevHalfWidth = halfWidth
        }
        
        return results
    }

}
